Skip to content

Boost.Exception:给 C++ 异常附加错误上下文 ​

C++ 异常对象携带的信息在构造时就固定了:throw my_error("failed") 抛出之后,中途的调用层想补充「是保存配置时失败的」这类上下文,标准异常没有手段。Boost.Exception 解决的就是这个问题:让异常在抛出点和任意一层 catch 点都能附加键值形式的信息,最后统一输出完整诊断。本文用一个完整示例把核心用法走一遍。

核心部件 ​

三样东西构成这套机制:

  • boost::exception 基类:异常类继承它之后获得携带附加信息的能力,定义在 boost/exception/exception.hpp;
  • boost::error_info<Tag, Type>:定义「信息键」的类型。第一个模板参数是一个标签结构体,仅用于在类型系统里区分不同的键;第二个参数是所存值的类型;
  • operator<< 与 diagnostic_information:前者在抛出点或 catch 点往异常上附加信息,后者把异常连同所有附加信息格式化成一段可读文本。

相关类和函数分散在多个头文件里,示例统一包含 boost/exception/all.hpp。

完整示例 ​

场景:一个分配内存的函数在失败时抛异常,中间层在转发前补一句「保存配置数据失败了」,最外层统一处理并打印诊断。

cpp
#include <boost/exception/all.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_array.hpp>
#include <exception>
#include <string>
#include <iostream>

// 定义两个信息键:tag 结构体只用来在类型上区分,值都是字符串
typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info;
typedef boost::error_info<struct tag_handle, std::string> errmsg_handle;

class allocation_failed : public boost::exception, public std::exception
{
public:
    allocation_failed(std::size_t size)
        : what_("allocation of " + boost::lexical_cast<std::string>(size) + " bytes failed")
    {
    }

    virtual const char *what() const throw()
    {
        return what_.c_str();
    }

private:
    std::string what_;
};

boost::shared_array<char> allocate(std::size_t size)
{
    if (size > 1000)
        // 抛出宏会自动附加函数名、文件名、行号
        BOOST_THROW_EXCEPTION(allocation_failed(size));
    return boost::shared_array<char>(new char[size]);
}

void save_configuration_data()
{
    try
    {
        boost::shared_array<char> a = allocate(2000);
        // saving configuration data ...
    }
    catch (boost::exception &e)
    {
        // 中间层补充上下文后原样上抛
        e << errmsg_info("saving configuration data failed");
        throw;
    }
}

void handle_eptr(std::exception_ptr eptr)
{
    if (eptr == nullptr) return;

    try
    {
        std::rethrow_exception(eptr);   // 按原类型重新抛出
    }
    catch (allocation_failed &e)
    {
        std::cerr << "Caught allocation_failed.\n"
                  << boost::diagnostic_information(e);
    }
    catch (const std::exception &e)
    {
        std::cerr << "Caught exception \"" << e.what() << "\"\n";
    }
}

int main()
{
    std::exception_ptr eptr;
    try
    {
        save_configuration_data();
    }
    catch (boost::exception &e)
    {
        e << errmsg_handle("Handle by handle_eptr");   // 最外层再补一条
        eptr = std::current_exception();               // 捕获为异常指针
    }

    handle_eptr(eptr);   // 统一异常处理
}

运行输出:

Caught allocation_failed.
boost_e.cpp(32): Throw in function boost::shared_array<char> allocate(std::size_t)
Dynamic exception type: boost::wrapexcept<allocation_failed>
std::exception::what: allocation of 2000 bytes failed
[tag_errmsg*] = saving configuration data failed
[tag_handle*] = Handle by handle_eptr

逐层拆解 ​

  • 抛出点:BOOST_THROW_EXCEPTION 宏除了抛出异常,还自动把函数名、文件名、行号作为附加信息记进去——输出里的 Throw in function ... 一行即来自这里;
  • 中间层:catch (boost::exception &e) 捕获后用 e << errmsg_info(...) 追加一条信息,再空 throw; 原样上抛,异常对象上已附加的信息全部保留;
  • 最外层:std::current_exception() 把当前异常捕获成 std::exception_ptr,交给统一的处理函数;std::rethrow_exception 恢复异常的原始类型,进入对应的 catch 分支;
  • 输出:diagnostic_information(e) 把异常动态类型、what() 内容、抛出位置和所有 error_info 键值一次性输出。只想取某一条时,可以用 boost::get_error_info<errmsg_info>(e),返回指向值的智能指针,判空后解引用使用。

细节与边界 ​

  • 继承关系:示例中的异常类同时继承 boost::exception 和 std::exception——继承前者获得附加信息的能力,继承后者保证能被通用的 catch (const std::exception &) 捕获、what() 可用。只继承 std::exception 也可以:BOOST_THROW_EXCEPTION 会通过 enable_error_info 机制为这类类型注入附加能力,同时继承两者的写法最直观;
  • catch 必须用引用:catch (boost::exception &e) 按引用捕获才能修改异常对象、附加信息后继续传递;按值捕获会切掉附加信息所在的派生部分;
  • 与标准库的关系:std::exception_ptr、std::current_exception、std::rethrow_exception 是 C++11 标准设施,解决「把异常存起来、换个地方再抛」的传递问题;而「给异常附加键值信息」在标准库里没有直接等价物。Boost.Exception 补上的是后一块,两者可以并用。

小结 ​

Boost.Exception 的价值集中在一点:异常的上下文可以沿途累积,而不必在抛出时一次凑齐。实践上三条建议:抛出统一走 BOOST_THROW_EXCEPTION,自动带上位置信息;中间层 catch 到就补业务上下文再上抛;最外层用 diagnostic_information 统一输出,配合日志系统定位问题。

最近更新

基于 VitePress 构建